package com.gtwm.jasperexecute;

//Copyright 2007 Oliver Kohll

//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at

//http://www.apache.org/licenses/LICENSE-2.0

//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.

import com.gtwm.jasperexecute.OptionValues.DatabaseType;
import com.gtwm.jasperexecute.OptionValues.OutputType;
import com.gtwm.jasperexecute.OptionValues.ParamType;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.xml.JRXmlLoader;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperExportManager;
import java.lang.IllegalArgumentException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.Address;
import javax.mail.Transport;
import javax.mail.BodyPart;
import javax.mail.internet.MimeBodyPart;
import javax.mail.Multipart;
import javax.mail.internet.MimeMultipart;
import java.sql.Connection;
import java.sql.DriverManager;
import javax.activation.FileDataSource;
import javax.activation.DataSource;
import javax.activation.DataHandler;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import java.util.Properties;
import java.sql.SQLException;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.io.FileUtils;

/**
 * Command line application to generate reports from JasperReports XML report
 * definition files, e.g. those designed with iReport
 * 
 * One or more report definitions can be specified on the command line and a PDF
 * or HTML file will be generated from each. If email details are specified,
 * then they'll be attached to an email and sent. If not, they'll just be
 * written to disk
 * 
 * Example Usage: java -jar RunJasperReports.jar -dbtype postgresql -dbname
 * accountsdb -dbuser fd -dbpass secret -reports p_and_l.jrxml,sales.jrxml
 * -folder /var/www/financial/ -emailto directors@gtwm.co.uk -emailfrom
 * accounts@gtwm.co.uk -subject FinancialReports -output pdf
 */
public class RunJasperReports {

	public RunJasperReports() {
	}

	public void generatePdfReport(String reportDefinitionFile, String outputFileName,
			DatabaseType dbType, String databaseName, String databaseUsername,
			String databasePassword, String databaseHost, Map parameters) throws FileNotFoundException, JRException,
			SQLException, IOException, ClassNotFoundException {
		JasperPrint print = generateReport(reportDefinitionFile, dbType, databaseName,
				databaseUsername, databasePassword, databaseHost, parameters);
		File outputFile = new File(outputFileName);
		outputFile.createNewFile();
		OutputStream output = new FileOutputStream(outputFile);
		JasperExportManager.exportReportToPdfStream(print, output);
		System.out.println("Output PDF file written: " + outputFileName);
	}

	public void generateHtmlReport(String reportDefinitionFile, String outputFileName,
			DatabaseType dbType, String databaseName, String databaseUsername,
			String databasePassword, String databaseHost, Map parameters) throws FileNotFoundException, JRException,
			SQLException, IOException, ClassNotFoundException {
		JasperPrint print = generateReport(reportDefinitionFile, dbType, databaseName,
				databaseUsername, databasePassword, databaseHost, parameters);
		JasperExportManager.exportReportToHtmlFile(print, outputFileName);
		// Images output by JasperReports don't have extensions, add them
		File imageFolder = new File(outputFileName + "_files");
		File[] imageFileArray = imageFolder.listFiles();
		if (imageFileArray != null) {
			List<File> imageFiles = Arrays.asList(imageFileArray);
			for (File imageFile : imageFiles) {
				if (!imageFile.getName().equals("px")) {
					File imageFileWithExtension = new File(imageFile.getAbsolutePath() + ".png");
					FileUtils.copyFile(imageFile, imageFileWithExtension, false);
				}
			}
		}
		System.out.println("Output HTML file written: " + outputFileName);
	}

	/**
	 * Perform a liberal check of boolean representation. Any string that starts
	 * with 't', 'y' or '1', case-insensitively, will return true - anything
	 * else, false, including null. Better than using Boolean.valueOf() because
	 * it is less stringent and will never throw an Exception
	 */
	private boolean valueRepresentsBooleanTrue(String value) {
		if (value == null) {
			return false;
		}
		if (value.toLowerCase(Locale.UK).startsWith("t")
				|| value.toLowerCase(Locale.UK).startsWith("y") || value.startsWith("1")) {
			return true;
		} else {
			return false;
		}
	}

	private Map prepareParameters(String parametersString) {
		Map parameters = new HashMap();
		if (parametersString == null) {
			return parameters;
		}
		List<String> parameterList = Arrays.asList(parametersString.split(","));
		for (String parameter : parameterList) {
			String paramName = parameter.split("=", 2)[0];
			String paramTypeAndValue = parameter.split("=", 2)[1];
			String paramTypeString = paramTypeAndValue.split(":", 2)[0];
			ParamType paramType = ParamType.valueOf(paramTypeString.toUpperCase());
			String paramValueString = paramTypeAndValue.split(":", 2)[1];
			switch (paramType) {
			case BOOLEAN:
				parameters.put(paramName, valueRepresentsBooleanTrue(paramValueString));
				break;
			case STRING:
				parameters.put(paramName, paramValueString);
				break;
			case DOUBLE:
				parameters.put(paramName, Double.valueOf(paramValueString));
				break;
			case INTEGER:
				parameters.put(paramName, Integer.valueOf(paramValueString));
				break;
			}
		}
		System.out.println("Report parameters are: " + parameters);
		return parameters;
	}

	private JasperPrint generateReport(String reportDefinitionFile, DatabaseType dbType,
			String databaseName, String databaseUsername, String databasePassword, String dbHost, Map parameters)
			throws FileNotFoundException, JRException, SQLException, ClassNotFoundException {
		// parse JasperReport XML file
		InputStream input = new FileInputStream(new File(reportDefinitionFile));
		JasperDesign design = JRXmlLoader.load(input);
		JasperReport report = JasperCompileManager.compileReport(design);
		// connect to db
		Class.forName(dbType.getDriverName());
		String connectionStatement = "jdbc:" + dbType.toString().toLowerCase() + "://" + dbHost + "/"
				+ databaseName;
		Connection conn = DriverManager.getConnection(connectionStatement, databaseUsername,
				databasePassword);
		conn.setAutoCommit(false);
		// run report and write output
		JasperPrint print = JasperFillManager.fillReport(report, parameters, conn);
		conn.close();
		return print;
	}

	public void emailReport(Set<String> emailRecipients, String emailSender, String emailSubject,
			List<String> attachmentFileNames) throws MessagingException {
		Properties props = new Properties();
		Session mailSession = Session.getDefaultInstance(props);
		MimeMessage message = new MimeMessage(mailSession);
		message.setSubject(emailSubject);
		for (String emailRecipient : emailRecipients) {
			Address toAddress = new InternetAddress(emailRecipient);
			message.addRecipient(Message.RecipientType.TO, toAddress);
		}
		Address fromAddress = new InternetAddress(emailSender);
		message.setFrom(fromAddress);
		// Message text
		Multipart multipart = new MimeMultipart();
		BodyPart textBodyPart = new MimeBodyPart();
		textBodyPart.setText("Database report attached\n\n");
		multipart.addBodyPart(textBodyPart);
		// Attachements
		for (String attachmentFileName : attachmentFileNames) {
			BodyPart attachmentBodyPart = new MimeBodyPart();
			DataSource source = new FileDataSource(attachmentFileName);
			attachmentBodyPart.setDataHandler(new DataHandler(source));
			String fileNameWithoutPath = attachmentFileName.replaceAll("^.*\\/", "");
			fileNameWithoutPath = fileNameWithoutPath.replaceAll("^.*\\\\", "");
			attachmentBodyPart.setFileName(fileNameWithoutPath);
			multipart.addBodyPart(attachmentBodyPart);
		}
		// add parts to message
		message.setContent(multipart);
		// send via SMTP
		Transport transport = mailSession.getTransport("smtp");
		transport.connect("localhost", null, null);
		transport.sendMessage(message, message.getAllRecipients());
		transport.close();
	}

	public static void main(String[] args) throws Exception {
		RunJasperReports runJasperReports = new RunJasperReports();
		// Set up command line parser
		Options options = new Options();
		Option reports = OptionBuilder.withArgName("reportlist").hasArg().withDescription(
				"Comma separated list of JasperReport XML input files").create("reports");
		options.addOption(reports);
		Option emailTo = OptionBuilder.withArgName("emailaddress").hasArg().withDescription(
				"Email address to send generated reports to").create("emailto");
		options.addOption(emailTo);
		Option emailFrom = OptionBuilder.withArgName("emailaddress").hasArg().withDescription(
				"Sender email address").create("emailfrom");
		options.addOption(emailFrom);
		Option emailSubjectLine = OptionBuilder.withArgName("emailsubject").hasArg()
				.withDescription("Subject line of email").create("subject");
		options.addOption(emailSubjectLine);
		Option outputFolder = OptionBuilder
				.withArgName("foldername")
				.hasArg()
				.withDescription(
						"Folder to write generated reports to, with trailing separator (slash or backslash)")
				.create("folder");
		options.addOption(outputFolder);
		Option dbTypeOption = OptionBuilder.withArgName("databasetype").hasArg().withDescription(
				"Currently 'postgresql' and 'mysql' are supported").create("dbtype");
		options.addOption(dbTypeOption);
		Option dbNameOption = OptionBuilder.withArgName("databasename").hasArg().withDescription(
				"Name of the database to run reports against").create("dbname");
		options.addOption(dbNameOption);

		Option dbUserOption = OptionBuilder.withArgName("username").hasArg().withDescription(
				"Username to connect to databasewith").create("dbuser");
		options.addOption(dbUserOption);
		Option dbPassOption = OptionBuilder.withArgName("password").hasArg().withDescription(
				"Database password").create("dbpass");
		options.addOption(dbPassOption);
		Option outputTypeOption = OptionBuilder.withArgName("outputtype").hasArg().withDescription(
				"Output type, 'pdf' or 'html'").create("output");
		options.addOption(outputTypeOption);
		Option dbHostOption = OptionBuilder.withArgName("host").hasArg().withDescription(
				"Database host address").create("dbhost");
		options.addOption(dbHostOption);
		Option paramsOption = OptionBuilder
				.withArgName("parameters")
				.hasArg()
				.withDescription(
						"Parameters, e.g. param1=boolean:true,param2=string:ABC,param3=double:134.2,param4=integer:85")
				.create("params");
		options.addOption(paramsOption);
		// Parse command line
		CommandLineParser parser = new GnuParser();
		CommandLine commandLine = parser.parse(options, args);
		String reportsDefinitionFileNamesCvs = commandLine.getOptionValue("reports");
		if (reportsDefinitionFileNamesCvs == null) {
			HelpFormatter formatter = new HelpFormatter();
			formatter.printHelp("java -jar RunJasperReports.jar", options);
			System.out.println();
			throw new IllegalArgumentException("No reports specified");
		}
		String outputPath = commandLine.getOptionValue("folder");
		List<String> reportDefinitionFileNames = Arrays.asList(reportsDefinitionFileNamesCvs
				.split(","));
		List<String> outputFileNames = new ArrayList<String>();
		DatabaseType databaseType = DatabaseType.valueOf(commandLine.getOptionValue("dbtype")
				.toUpperCase());
		String databaseName = commandLine.getOptionValue("dbname");
		String databaseUsername = commandLine.getOptionValue("dbuser");
		String databasePassword = commandLine.getOptionValue("dbpass");
		String databaseHost = commandLine.getOptionValue("dbhost");
		if (databaseHost == null) {
			databaseHost = "localhost";
		}
		OutputType outputType = OutputType.valueOf(commandLine.getOptionValue("output")
				.toUpperCase());
		String parametersString = commandLine.getOptionValue("params");
		Map parameters = runJasperReports.prepareParameters(parametersString);
		// Iterate over reports, generating output for each
		for (String reportsDefinitionFileName : reportDefinitionFileNames) {
			String outputFileName = reportsDefinitionFileName.replaceAll("\\..*$", "");
			outputFileName = outputFileName.replaceAll("^.*\\/", "");
			outputFileName = outputFileName.replaceAll("\\W", "").toLowerCase() + "."
					+ outputType.toString().toLowerCase();
			if (outputPath != null) {
				outputFileName = outputPath + outputFileName;
			}
			System.out.println("Generating report " + outputFileName);
			if (outputType.equals(OutputType.PDF)) {
				runJasperReports.generatePdfReport(reportsDefinitionFileName, outputFileName,
						databaseType, databaseName, databaseUsername, databasePassword, databaseHost, parameters);
			} else {
				runJasperReports.generateHtmlReport(reportsDefinitionFileName, outputFileName,
						databaseType, databaseName, databaseUsername, databasePassword, databaseHost, parameters);
			}
			outputFileNames.add(outputFileName);
		}
		String emailRecipientList = commandLine.getOptionValue("emailto");
		if (emailRecipientList != null) {
			Set<String> emailRecipients = new HashSet<String>(Arrays.asList(emailRecipientList
					.split(",")));
			String emailSender = commandLine.getOptionValue("emailfrom");
			String emailSubject = commandLine.getOptionValue("subject");
			System.out.println("Emailing reports to " + emailRecipients);
			runJasperReports.emailReport(emailRecipients, emailSender, emailSubject,
					outputFileNames);
		}
	}

}
